Machine Learning review and intro to tidymodels
Read through and follow along with the Machine Learning review with an intro to the tidymodels package posted on the Course Materials page.
Tasks:
- Read about the hotel booking data,
hotels, on the Tidy Tuesday page it came from. There is also a link to an article from the original authors. The outcome we will be predicting is called is_canceled.
Without doing any analysis, what are some variables you think might be predictive and why?
previous_cancellations and previous_bookings_not_canceled would indicate any patterns if a person cancels frequently
lead_time - if people book farther in advance, their plans are more likely to change
children and babies - both make people more likely to cancel due to illness
_ What are some problems that might exist with the data? You might think about how it was collected and who did the collecting.
The dataset only looks at two different hotels, with significantly more reservations at the city hotel than the resort hotel.
- If we construct a model, what type of conclusions will be able to draw from it?
how likely someone is to cancel their booking based on the predictor variables.
- Create some exploratory plots or table summaries of the variables in the dataset. Be sure to also examine missing values or other interesting values. You may want to adjust the
fig.width and fig.height in the code chunk options.
hotels %>%
select(where(is.numeric)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_histogram(bins = 30) +
facet_wrap(vars(variable),
scales = "free")

Many of the quantitative variables are right skewed or have large outlier values, especially adults, average daily rate (adr), previous_cancellations, and bookings_not_cancelled. Those that are right skewed should possibly be log transformed. There are also several binary variables being read as quantitative here, which need to be kept in mind.
hotels %>%
select(where(is.character)) %>%
pivot_longer(cols = everything(),
names_to = "variable",
values_to = "value") %>%
ggplot(aes(x = value)) +
geom_bar() +
facet_wrap(vars(variable),
scales = "free",
nrow = 2)
> Notably, about a third of the bookings were cancelled. 2/3 of the data came from the city hotel.
- First, we will do a couple things to get the data ready.
I did the following for you: made outcome a factor (needs to be that way for logistic regression), made all character variables factors, removed the year variable and some reservation status variables, and removed cases with missing values (not NULLs but true missing values).
You need to split the data into a training and test set, stratifying on the outcome variable, is_canceled. Since we have a lot of data, split the data 50/50 between training and test. I have already set.seed() for you. Be sure to use hotels_mod in the splitting.
hotels_mod <- hotels %>%
mutate(is_canceled = as.factor(is_canceled)) %>%
mutate(across(where(is.character), as.factor)) %>%
select(-arrival_date_year,
-reservation_status,
-reservation_status_date) %>%
add_n_miss() %>%
filter(n_miss_all == 0) %>%
select(-n_miss_all)
set.seed(494)
# Assign 50% of data to training
hotels_mod_split <- initial_split(hotels_mod,
prop = 0.5,
strata = is_canceled)
hotels_training <- training(hotels_mod_split)
hotels_testing <- testing(hotels_mod_split)
- In this next step, we are going to do the pre-processing. Usually, I won’t tell you exactly what to do here, but for your first exercise, I’ll tell you the steps.
- Set up the recipe with
is_canceled as the outcome and all other variables as predictors (HINT: ~.).
- Use a
step_XXX() function or functions (I think there are other ways to do this, but I found step_mutate_at() easiest) to create some indicator variables for the following variables: children, babies, and previous_cancellations. So, the new variable should be a 1 if the original is more than 0 and 0 otherwise. Make sure you do this in a way that accounts for values that may be larger than any we see in the dataset.
- For the
agent and company variables, make new indicator variables that are 1 if they have a value of NULL and 0 otherwise. I also used step_mutate_at() for this, but there’s more ways you could do it.
- Use
fct_lump_n() inside step_mutate() to lump together countries that aren’t in the top 5 most occurring.
- If you used new names for some of the new variables you created, then remove any variables that are no longer needed.
- Use
step_normalize() to center and scale all the non-categorical predictor variables. (Do this BEFORE creating dummy variables. When I tried to do it after, I ran into an error - I’m still investigating why.)
- Create dummy variables for all factors/categorical predictor variables (make sure you have
-all_outcomes() in this part!!).
- Use the
prep() and juice() functions to apply the steps to the training data just to check that everything went as planned.
hotels_recipe <- recipe(is_canceled ~ ., #short-cut, . = all other vars
data = hotels_training) %>%
# Pre-processing:
#add indicator variables
step_mutate(children_indicator = ifelse(children > 0, 1, 0),
babies_indicator = ifelse(babies > 0, 1, 0),
previous_cancellations_indicator = ifelse(previous_cancellations > 0, 1, 0)) %>%
step_mutate(agent_indicator = ifelse(agent == "NULL", 1, 0),
company_indicator = ifelse(company == "NULL", 1, 0)) %>%
#lump together countries that aren't in the top 5
step_mutate(country = fct_lump_n(country, 5, w = NULL, other_level = "OTHER")) %>%
step_rm(children, babies, previous_cancellations, agent, company) %>% #needs to be before step_normalize or next code chunk won't work
#use step_normalize
step_normalize(all_predictors(),
-all_nominal()) %>%
#create dummy variables
step_dummy(all_nominal(),
-all_outcomes())
hotels_recipe %>%
prep(hotels_training) %>%
juice()
- In this step we will set up a LASSO model and workflow.
- In general, why would we want to use LASSO instead of regular logistic regression? (HINT: think about what happens to the coefficients).
LASSO models rein in runaway coefficients and standardize all variables to be on a scal with a mean of 0 and standard deviation of 1 while weaning out bad predictors.
- Define the model type, set the engine, set the
penalty argument to tune() as a placeholder, and set the mode.
hotels_lasso_mod <-
# Define a lasso model
logistic_reg(mixture = 1) %>%
# Set the engine to "glmnet"
set_engine("glmnet") %>%
# The parameters we will tune.
set_args(penalty = tune()) %>%
set_mode("classification")
- Create a workflow with the recipe and model.
hotels_lasso_wf <-
# Set up the workflow
workflow() %>%
# Add the recipe
add_recipe(hotels_recipe) %>%
# Add the modeling
add_model(hotels_lasso_mod)
hotels_lasso_wf
## ══ Workflow ════════════════════════════════════════════════════════════════════
## Preprocessor: Recipe
## Model: logistic_reg()
##
## ── Preprocessor ────────────────────────────────────────────────────────────────
## 6 Recipe Steps
##
## • step_mutate()
## • step_mutate()
## • step_mutate()
## • step_rm()
## • step_normalize()
## • step_dummy()
##
## ── Model ───────────────────────────────────────────────────────────────────────
## Logistic Regression Model Specification (classification)
##
## Main Arguments:
## penalty = tune()
## mixture = 1
##
## Computational engine: glmnet
- In this step, we’ll tune the model and fit the model using the best tuning parameter to the entire training dataset.
- Create a 5-fold cross-validation sample. We’ll use this later. I have set the seed for you.
- Use the
grid_regular() function to create a grid of 10 potential penalty parameters (we’re keeping this sort of small because the dataset is pretty large). Use that with the 5-fold cv data to tune the model.
- Use the
tune_grid() function to fit the models with different tuning parameters to the different cross-validation sets.
- Use the
collect_metrics() function to collect all the metrics from the previous step and create a plot with the accuracy on the y-axis and the penalty term on the x-axis. Put the x-axis on the log scale.
- Use the
select_best() function to find the best tuning parameter, fit the model using that tuning parameter to the entire training set (HINT: finalize_workflow() and fit()), and display the model results using pull_workflow_fit() and tidy(). Are there some variables with coefficients of 0?
set.seed(494) # for reproducibility
#cross-validation sample
hotels_cv <- vfold_cv(hotels_training, v = 5)
#penalty parameters
penalty_grid <- grid_regular(penalty(),
levels = 10)
#tune_grid()
hotels_lasso_tune <-
hotels_lasso_wf %>%
tune_grid(
resamples = hotels_cv,
grid = penalty_grid
)
#collect_metrics()
collect_metrics(hotels_lasso_tune)
#make a plot
hotels_lasso_tune %>%
collect_metrics() %>%
filter(.metric == "accuracy") %>%
ggplot(aes(x = penalty, y = mean)) +
geom_point() +
geom_line() +
scale_x_log10(
breaks = scales::trans_breaks("log10", function(x) 10^x),
labels = scales::trans_format("log10",scales::math_format(10^.x))) +
labs(x = "penalty (log)", y = "accuracy")

#select_best()
best_param <- hotels_lasso_tune %>%
select_best(metric = "accuracy")
#Create final workflow
hotels_lasso_final_wf <- hotels_lasso_wf %>%
finalize_workflow(best_param)
hotels_lasso_final_mod <- hotels_lasso_final_wf %>%
fit(data = hotels_training)
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
tidy()
Arrival_date_month_February, market_segment_Groups, distribution_channel_Undefined, and assigned_room_type_L each have a coefficient of 0.
- Now that we have a model, let’s evaluate it a bit more. All we have looked at so far is the cross-validated accuracy from the previous step.
- Create a variable importance graph. Which variables show up as the most important? Are you surprised?
In general, reserved and assigned room types appear to be among the most important variables (which I was not expecting). The deposit being non-refundable is the second most important, which is expected. For some reason, the tenth most important variable is if the guests are from Portugal, which is also surprising.
# Visualize variable importance
hotels_lasso_final_mod %>%
pull_workflow_fit() %>%
vip()

- Use the
last_fit() function to fit the final model and then apply it to the testing data. Report the metrics from the testing data using the collect_metrics() function. How do they compare to the cross-validated metrics?
They are very similar to the cross-validated metrics - these metrics are each about 0.002 lower than the estimates from the cross-validated metrics.
hotels_lasso_test <- hotels_lasso_final_wf %>%
last_fit(hotels_mod_split)
hotels_lasso_test %>%
collect_metrics()
#cross-validated metrics for comparison
collect_metrics(hotels_lasso_tune) %>%
head(2)
- Use the
collect_predictions() function to find the predicted probabilities and classes for the test data. Save this to a new dataset called preds. Then, use the conf_mat() function from dials (part of tidymodels) to create a confusion matrix showing the predicted classes vs. the true classes. Compute the true positive rate (sensitivity), true negative rate (specificity), and accuracy. See this Wikipedia reference if you (like me) tend to forget these definitions. Also keep in mind that a “positive” in this case is a cancellation (those are the 1’s).
preds <-
collect_predictions(hotels_lasso_test)
preds %>%
conf_mat(truth = is_canceled, estimate = .pred_class)
## Truth
## Prediction 0 1
## 0 34179 7777
## 1 3404 14333
Sensitivity (true positive rate) = true positive/(true positive + false negative) = 14,333/(14,333+7777) = 0.6482587 = 64.83%
Specificity (true negative rate) = true negative/(true negative + false positive) = 34,179/(34,179+3404) = 0.9094271 = 90.94%
Accuracy = (true positive + true negative)/total = 0.8126916 = 81.27%
- Use the
preds dataset you just created to create a density plot of the predicted probabilities of canceling (the variable is called .pred_1), filling by is_canceled. Use an alpha = .5 and color = NA in the geom_density().
preds %>%
ggplot(aes(x = .pred_1, fill = is_canceled)) +
geom_density(alpha = .5, color = NA) +
labs(x = "predicted probabilities of cancelling")

Answer these questions: a. What would this graph look like for a model with an accuracy that was close to 1?
A model with an accuracy close to 1 would have the reds (not cancelled) be very dense at 0 (0% probability of cancelled) and less dense elsewhere. The blues would be very dense at 1 and less dense elsewhere. The tails of both would stop near 0.5 (or whatever the threshold was set for)
- Our predictions are classified as canceled if their predicted probability of canceling is greater than .5. If we wanted to have a high true positive rate, should we make the cutoff for predicted as canceled higher or lower than .5?
We should make it lower than 0.5 to capture more of the cancelled visits.
- What happens to the true negative rate if we try to get a higher true positive rate?
The true negative rate would decrease.
- Let’s say that this model is going to be applied to bookings 14 days in advance of their arrival at each hotel, and someone who works for the hotel will make a phone call to the person who made the booking. During this phone call, they will try to ensure that the person will be keeping their reservation or that they will be canceling in which case they can do that now and still have time to fill the room. How should the hotel go about deciding who to call? How could they measure whether it was worth the effort to do the calling? Can you think of another way they might use the model?
The hotel could use the model to identify and call people with a predicted probability of cancelling above a certain threshold (say, 0.5). To measure whether it was worth the effort to do the calling, the hotel could keep track of the rate of people called that cancelled vs the rate of people who were not called who ended up cancelling. It’s also worth considering that the phone call may be a reminder to people who somehow forgot about their reservation, so the model should be reworked with the new data overtime. Although I think it would be inappropriate and likely illegal, the model could be used to set the deposit amount on a sliding scale. Those who are more likely to cancel could be asked to pay a higher deposit, decreasing the likelihood that they will cancel and decreasing the hotel’s losses if they do.
- How might you go about questioning and evaluating the model in terms of fairness? Are there any questions you would like to ask of the people who collected the data?
It would be important to consider the demographics of the people collected, especially because what country they’re from is a predictor. Because we’re only considering the 5 most common countries and all others are lumped together, the information could be skewed enough that people from certain countries are targeted. I think it’s also concerning how uneven the divide of data between the two hotels was - about 2/3 came from the city hotel, meaning it and its guests were overrepresented. I’d be curious to ask how those two hotels in particular were chosen to be part of the dataset, whether there were any differences in summer as opposed to other seasons (because summer months were overrepresented), and if there were any variables they considered including but didn’t.
LS0tCnRpdGxlOiAnQXNzaWdubWVudCAjMicKYXV0aG9yOiAiTWlhIFJvdGhiZXJnIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSkKYGBgCgpgYGB7ciBsaWJyYXJpZXN9CmxpYnJhcnkodGlkeXZlcnNlKSAgICAgICAgICMgZm9yIGdyYXBoaW5nIGFuZCBkYXRhIGNsZWFuaW5nCmxpYnJhcnkodGlkeW1vZGVscykgICAgICAgICMgZm9yIG1vZGVsaW5nCmxpYnJhcnkobmFuaWFyKSAgICAgICAgICAgICMgZm9yIGFuYWx5emluZyBtaXNzaW5nIHZhbHVlcwpsaWJyYXJ5KHZpcCkgICAgICAgICAgICAgICAjIGZvciB2YXJpYWJsZSBpbXBvcnRhbmNlIHBsb3RzCnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkpICMgTGlzYSdzIGZhdm9yaXRlIHRoZW1lCmBgYAoKYGBge3IgZGF0YX0KaG90ZWxzIDwtIHJlYWRyOjpyZWFkX2NzdignaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Jmb3JkYXRhc2NpZW5jZS90aWR5dHVlc2RheS9tYXN0ZXIvZGF0YS8yMDIwLzIwMjAtMDItMTEvaG90ZWxzLmNzdicpCmBgYAoKCldoZW4geW91IGZpbmlzaCB0aGUgYXNzaWdubWVudCwgcmVtb3ZlIHRoZSBgI2AgZnJvbSB0aGUgb3B0aW9ucyBjaHVuayBhdCB0aGUgdG9wLCBzbyB0aGF0IG1lc3NhZ2VzIGFuZCB3YXJuaW5ncyBhcmVuJ3QgcHJpbnRlZC4gSWYgeW91IGFyZSBnZXR0aW5nIGVycm9ycyBpbiB5b3VyIGNvZGUsIGFkZCBgZXJyb3IgPSBUUlVFYCBzbyB0aGF0IHRoZSBmaWxlIGtuaXRzLiBJIHdvdWxkIHJlY29tbWVuZCBub3QgcmVtb3ZpbmcgdGhlIGAjYCB1bnRpbCB5b3UgYXJlIGNvbXBsZXRlbHkgZmluaXNoZWQuCgojIyBQdXQgaXQgb24gR2l0SHViISAgICAgICAgCgpGcm9tIG5vdyBvbiwgR2l0SHViIHNob3VsZCBiZSBwYXJ0IG9mIHlvdXIgcm91dGluZSB3aGVuIGRvaW5nIGFzc2lnbm1lbnRzLiBJIHJlY29tbWVuZCBtYWtpbmcgaXQgcGFydCBvZiB5b3VyIHByb2Nlc3MgYW55dGltZSB5b3UgYXJlIHdvcmtpbmcgaW4gUiwgYnV0IEknbGwgbWFrZSB5b3Ugc2hvdyBpdCdzIHBhcnQgb2YgeW91ciBwcm9jZXNzIGZvciBhc3NpZ25tZW50cy4KCioqVGFzayoqOiBXaGVuIHlvdSBhcmUgZmluaXNoZWQgd2l0aCB0aGUgYXNzaWdubWVudCwgcG9zdCBhIGxpbmsgYmVsb3cgdG8gdGhlIEdpdEh1YiByZXBvIGZvciB0aGUgYXNzaWdubWVudC4KCltHaXRodWIgbGlua10oaHR0cHM6Ly9naXRodWIuY29tL21pYXJvdGhiZXJnL2Fzc2lnbm1lbnRfMDJfMDkxNjIxKQoKIyMgTWFjaGluZSBMZWFybmluZyByZXZpZXcgYW5kIGludHJvIHRvIGB0aWR5bW9kZWxzYAoKUmVhZCB0aHJvdWdoIGFuZCBmb2xsb3cgYWxvbmcgd2l0aCB0aGUgW01hY2hpbmUgTGVhcm5pbmcgcmV2aWV3IHdpdGggYW4gaW50cm8gdG8gdGhlIGB0aWR5bW9kZWxzYCBwYWNrYWdlXShodHRwczovL2FkdmFuY2VkLWRzLWluLXIubmV0bGlmeS5hcHAvcG9zdHMvMjAyMS0wMy0xNi1tbC1yZXZpZXcvKSBwb3N0ZWQgb24gdGhlIENvdXJzZSBNYXRlcmlhbHMgcGFnZS4gCgoqKlRhc2tzKio6CgoxLiBSZWFkIGFib3V0IHRoZSBob3RlbCBib29raW5nIGRhdGEsIGBob3RlbHNgLCBvbiB0aGUgW1RpZHkgVHVlc2RheSBwYWdlXShodHRwczovL2dpdGh1Yi5jb20vcmZvcmRhdGFzY2llbmNlL3RpZHl0dWVzZGF5L2Jsb2IvbWFzdGVyL2RhdGEvMjAyMC8yMDIwLTAyLTExL3JlYWRtZS5tZCkgaXQgY2FtZSBmcm9tLiBUaGVyZSBpcyBhbHNvIGEgbGluayB0byBhbiBhcnRpY2xlIGZyb20gdGhlIG9yaWdpbmFsIGF1dGhvcnMuIFRoZSBvdXRjb21lIHdlIHdpbGwgYmUgcHJlZGljdGluZyBpcyBjYWxsZWQgYGlzX2NhbmNlbGVkYC4gCiAgLSBXaXRob3V0IGRvaW5nIGFueSBhbmFseXNpcywgd2hhdCBhcmUgc29tZSB2YXJpYWJsZXMgeW91IHRoaW5rIG1pZ2h0IGJlIHByZWRpY3RpdmUgYW5kIHdoeT8gCiAgCiAgKiBwcmV2aW91c19jYW5jZWxsYXRpb25zIGFuZCBwcmV2aW91c19ib29raW5nc19ub3RfY2FuY2VsZWQgd291bGQgaW5kaWNhdGUgYW55IHBhdHRlcm5zIGlmIGEgcGVyc29uIGNhbmNlbHMgZnJlcXVlbnRseQogICogbGVhZF90aW1lIC0gaWYgcGVvcGxlIGJvb2sgZmFydGhlciBpbiBhZHZhbmNlLCB0aGVpciBwbGFucyBhcmUgbW9yZSBsaWtlbHkgdG8gY2hhbmdlCiAgKiBjaGlsZHJlbiBhbmQgYmFiaWVzIC0gYm90aCBtYWtlIHBlb3BsZSBtb3JlIGxpa2VseSB0byBjYW5jZWwgZHVlIHRvIGlsbG5lc3MKICAKICBfIFdoYXQgYXJlIHNvbWUgcHJvYmxlbXMgdGhhdCBtaWdodCBleGlzdCB3aXRoIHRoZSBkYXRhPyBZb3UgbWlnaHQgdGhpbmsgYWJvdXQgaG93IGl0IHdhcyBjb2xsZWN0ZWQgYW5kIHdobyBkaWQgdGhlIGNvbGxlY3RpbmcuICAKICAKICA+VGhlIGRhdGFzZXQgb25seSBsb29rcyBhdCB0d28gZGlmZmVyZW50IGhvdGVscywgd2l0aCBzaWduaWZpY2FudGx5IG1vcmUgcmVzZXJ2YXRpb25zIGF0IHRoZSBjaXR5IGhvdGVsIHRoYW4gdGhlIHJlc29ydCBob3RlbC4KICAKICAtIElmIHdlIGNvbnN0cnVjdCBhIG1vZGVsLCB3aGF0IHR5cGUgb2YgY29uY2x1c2lvbnMgd2lsbCBiZSBhYmxlIHRvIGRyYXcgZnJvbSBpdD8gIAogIAogID5ob3cgbGlrZWx5IHNvbWVvbmUgaXMgdG8gY2FuY2VsIHRoZWlyIGJvb2tpbmcgYmFzZWQgb24gdGhlIHByZWRpY3RvciB2YXJpYWJsZXMuCiAgCjIuIENyZWF0ZSBzb21lIGV4cGxvcmF0b3J5IHBsb3RzIG9yIHRhYmxlIHN1bW1hcmllcyBvZiB0aGUgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0LiBCZSBzdXJlIHRvIGFsc28gZXhhbWluZSBtaXNzaW5nIHZhbHVlcyBvciBvdGhlciBpbnRlcmVzdGluZyB2YWx1ZXMuIFlvdSBtYXkgd2FudCB0byBhZGp1c3QgdGhlIGBmaWcud2lkdGhgIGFuZCBgZmlnLmhlaWdodGAgaW4gdGhlIGNvZGUgY2h1bmsgb3B0aW9ucy4gIAoKYGBge3IgZXhwbF9xdWFudH0KaG90ZWxzICU+JSAKICBzZWxlY3Qod2hlcmUoaXMubnVtZXJpYykpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbnMgPSAzMCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiKQpgYGAKCj4gTWFueSBvZiB0aGUgcXVhbnRpdGF0aXZlIHZhcmlhYmxlcyBhcmUgcmlnaHQgc2tld2VkIG9yIGhhdmUgbGFyZ2Ugb3V0bGllciB2YWx1ZXMsIGVzcGVjaWFsbHkgYWR1bHRzLCBhdmVyYWdlIGRhaWx5IHJhdGUgKGFkciksIHByZXZpb3VzX2NhbmNlbGxhdGlvbnMsIGFuZCBib29raW5nc19ub3RfY2FuY2VsbGVkLiBUaG9zZSB0aGF0IGFyZSByaWdodCBza2V3ZWQgc2hvdWxkIHBvc3NpYmx5IGJlIGxvZyB0cmFuc2Zvcm1lZC4gVGhlcmUgYXJlIGFsc28gc2V2ZXJhbCBiaW5hcnkgdmFyaWFibGVzIGJlaW5nIHJlYWQgYXMgcXVhbnRpdGF0aXZlIGhlcmUsIHdoaWNoIG5lZWQgdG8gYmUga2VwdCBpbiBtaW5kLgoKYGBge3IgZXhwbF9jYXQsIGZpZy53aWR0aD04LCBmaWcuaGVpZ2h0PTh9CmhvdGVscyAlPiUgCiAgc2VsZWN0KHdoZXJlKGlzLmNoYXJhY3RlcikpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IGV2ZXJ5dGhpbmcoKSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGUiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gInZhbHVlIikgJT4lIAogIGdncGxvdChhZXMoeCA9IHZhbHVlKSkgKwogIGdlb21fYmFyKCkgKwogIGZhY2V0X3dyYXAodmFycyh2YXJpYWJsZSksIAogICAgICAgICAgICAgc2NhbGVzID0gImZyZWUiLCAKICAgICAgICAgICAgIG5yb3cgPSAyKQpgYGAKPiBOb3RhYmx5LCBhYm91dCBhIHRoaXJkIG9mIHRoZSBib29raW5ncyB3ZXJlIGNhbmNlbGxlZC4gMi8zIG9mIHRoZSBkYXRhIGNhbWUgZnJvbSB0aGUgY2l0eSBob3RlbC4KCjMuIEZpcnN0LCB3ZSB3aWxsIGRvIGEgY291cGxlIHRoaW5ncyB0byBnZXQgdGhlIGRhdGEgcmVhZHkuIAoKKiBJIGRpZCB0aGUgZm9sbG93aW5nIGZvciB5b3U6IG1hZGUgb3V0Y29tZSBhIGZhY3RvciAobmVlZHMgdG8gYmUgdGhhdCB3YXkgZm9yIGxvZ2lzdGljIHJlZ3Jlc3Npb24pLCBtYWRlIGFsbCBjaGFyYWN0ZXIgdmFyaWFibGVzIGZhY3RvcnMsIHJlbW92ZWQgdGhlIHllYXIgdmFyaWFibGUgYW5kIHNvbWUgcmVzZXJ2YXRpb24gc3RhdHVzIHZhcmlhYmxlcywgYW5kIHJlbW92ZWQgY2FzZXMgd2l0aCBtaXNzaW5nIHZhbHVlcyAobm90IE5VTExzIGJ1dCB0cnVlIG1pc3NpbmcgdmFsdWVzKS4KCiogWW91IG5lZWQgdG8gc3BsaXQgdGhlIGRhdGEgaW50byBhIHRyYWluaW5nIGFuZCB0ZXN0IHNldCwgc3RyYXRpZnlpbmcgb24gdGhlIG91dGNvbWUgdmFyaWFibGUsIGBpc19jYW5jZWxlZGAuIFNpbmNlIHdlIGhhdmUgYSBsb3Qgb2YgZGF0YSwgc3BsaXQgdGhlIGRhdGEgNTAvNTAgYmV0d2VlbiB0cmFpbmluZyBhbmQgdGVzdC4gSSBoYXZlIGFscmVhZHkgYHNldC5zZWVkKClgIGZvciB5b3UuIEJlIHN1cmUgdG8gdXNlIGBob3RlbHNfbW9kYCBpbiB0aGUgc3BsaXR0aW5nLgoKYGBge3J9CmhvdGVsc19tb2QgPC0gaG90ZWxzICU+JSAKICBtdXRhdGUoaXNfY2FuY2VsZWQgPSBhcy5mYWN0b3IoaXNfY2FuY2VsZWQpKSAlPiUgCiAgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5jaGFyYWN0ZXIpLCBhcy5mYWN0b3IpKSAlPiUgCiAgc2VsZWN0KC1hcnJpdmFsX2RhdGVfeWVhciwKICAgICAgICAgLXJlc2VydmF0aW9uX3N0YXR1cywKICAgICAgICAgLXJlc2VydmF0aW9uX3N0YXR1c19kYXRlKSAlPiUgCiAgYWRkX25fbWlzcygpICU+JSAKICBmaWx0ZXIobl9taXNzX2FsbCA9PSAwKSAlPiUgCiAgc2VsZWN0KC1uX21pc3NfYWxsKQoKc2V0LnNlZWQoNDk0KQoKIyBBc3NpZ24gNTAlIG9mIGRhdGEgdG8gdHJhaW5pbmcKaG90ZWxzX21vZF9zcGxpdCA8LSBpbml0aWFsX3NwbGl0KGhvdGVsc19tb2QsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByb3AgPSAwLjUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyYXRhID0gaXNfY2FuY2VsZWQpCmhvdGVsc190cmFpbmluZyA8LSB0cmFpbmluZyhob3RlbHNfbW9kX3NwbGl0KQpob3RlbHNfdGVzdGluZyA8LSB0ZXN0aW5nKGhvdGVsc19tb2Rfc3BsaXQpCmBgYAoKNC4gSW4gdGhpcyBuZXh0IHN0ZXAsIHdlIGFyZSBnb2luZyB0byBkbyB0aGUgcHJlLXByb2Nlc3NpbmcuIFVzdWFsbHksIEkgd29uJ3QgdGVsbCB5b3UgZXhhY3RseSB3aGF0IHRvIGRvIGhlcmUsIGJ1dCBmb3IgeW91ciBmaXJzdCBleGVyY2lzZSwgSSdsbCB0ZWxsIHlvdSB0aGUgc3RlcHMuIAoKKiBTZXQgdXAgdGhlIHJlY2lwZSB3aXRoIGBpc19jYW5jZWxlZGAgYXMgdGhlIG91dGNvbWUgYW5kIGFsbCBvdGhlciB2YXJpYWJsZXMgYXMgcHJlZGljdG9ycyAoSElOVDogYH4uYCkuICAKKiBVc2UgYSBgc3RlcF9YWFgoKWAgZnVuY3Rpb24gb3IgZnVuY3Rpb25zIChJIHRoaW5rIHRoZXJlIGFyZSBvdGhlciB3YXlzIHRvIGRvIHRoaXMsIGJ1dCBJIGZvdW5kIGBzdGVwX211dGF0ZV9hdCgpYCBlYXNpZXN0KSB0byBjcmVhdGUgc29tZSBpbmRpY2F0b3IgdmFyaWFibGVzIGZvciB0aGUgZm9sbG93aW5nIHZhcmlhYmxlczogYGNoaWxkcmVuYCwgYGJhYmllc2AsIGFuZCBgcHJldmlvdXNfY2FuY2VsbGF0aW9uc2AuIFNvLCB0aGUgbmV3IHZhcmlhYmxlIHNob3VsZCBiZSBhIDEgaWYgdGhlIG9yaWdpbmFsIGlzIG1vcmUgdGhhbiAwIGFuZCAwIG90aGVyd2lzZS4gTWFrZSBzdXJlIHlvdSBkbyB0aGlzIGluIGEgd2F5IHRoYXQgYWNjb3VudHMgZm9yIHZhbHVlcyB0aGF0IG1heSBiZSBsYXJnZXIgdGhhbiBhbnkgd2Ugc2VlIGluIHRoZSBkYXRhc2V0LiAgCiogRm9yIHRoZSBgYWdlbnRgIGFuZCBgY29tcGFueWAgdmFyaWFibGVzLCBtYWtlIG5ldyBpbmRpY2F0b3IgdmFyaWFibGVzIHRoYXQgYXJlIDEgaWYgdGhleSBoYXZlIGEgdmFsdWUgb2YgYE5VTExgIGFuZCAwIG90aGVyd2lzZS4gSSBhbHNvIHVzZWQgYHN0ZXBfbXV0YXRlX2F0KClgIGZvciB0aGlzLCBidXQgdGhlcmUncyBtb3JlIHdheXMgeW91IGNvdWxkIGRvIGl0LgoqIFVzZSBgZmN0X2x1bXBfbigpYCBpbnNpZGUgYHN0ZXBfbXV0YXRlKClgIHRvIGx1bXAgdG9nZXRoZXIgY291bnRyaWVzIHRoYXQgYXJlbid0IGluIHRoZSB0b3AgNSBtb3N0IG9jY3VycmluZy4gCiogSWYgeW91IHVzZWQgbmV3IG5hbWVzIGZvciBzb21lIG9mIHRoZSBuZXcgdmFyaWFibGVzIHlvdSBjcmVhdGVkLCB0aGVuIHJlbW92ZSBhbnkgdmFyaWFibGVzIHRoYXQgYXJlIG5vIGxvbmdlciBuZWVkZWQuIAoqIFVzZSBgc3RlcF9ub3JtYWxpemUoKWAgdG8gY2VudGVyIGFuZCBzY2FsZSBhbGwgdGhlIG5vbi1jYXRlZ29yaWNhbCBwcmVkaWN0b3IgdmFyaWFibGVzLiAoRG8gdGhpcyBCRUZPUkUgY3JlYXRpbmcgZHVtbXkgdmFyaWFibGVzLiBXaGVuIEkgdHJpZWQgdG8gZG8gaXQgYWZ0ZXIsIEkgcmFuIGludG8gYW4gZXJyb3IgLSBJJ20gc3RpbGwgW2ludmVzdGlnYXRpbmddKGh0dHBzOi8vY29tbXVuaXR5LnJzdHVkaW8uY29tL3QvdGlkeW1vZGVscy1zZWUtbm90ZXMtZXJyb3ItYnV0LW9ubHktd2l0aC1zdGVwLXh4eC1mdW5jdGlvbnMtaW4tYS1jZXJ0YWluLW9yZGVyLzExNTAwNikgd2h5LikKKiBDcmVhdGUgZHVtbXkgdmFyaWFibGVzIGZvciBhbGwgZmFjdG9ycy9jYXRlZ29yaWNhbCBwcmVkaWN0b3IgdmFyaWFibGVzIChtYWtlIHN1cmUgeW91IGhhdmUgYC1hbGxfb3V0Y29tZXMoKWAgaW4gdGhpcyBwYXJ0ISEpLiAgCiogVXNlIHRoZSBgcHJlcCgpYCBhbmQgYGp1aWNlKClgIGZ1bmN0aW9ucyB0byBhcHBseSB0aGUgc3RlcHMgdG8gdGhlIHRyYWluaW5nIGRhdGEganVzdCB0byBjaGVjayB0aGF0IGV2ZXJ5dGhpbmcgd2VudCBhcyBwbGFubmVkLgoKYGBge3IgcmVjaXBlfQoKaG90ZWxzX3JlY2lwZSA8LSByZWNpcGUoaXNfY2FuY2VsZWQgfiAuLCAjc2hvcnQtY3V0LCAuID0gYWxsIG90aGVyIHZhcnMKICAgICAgICAgICAgICAgICAgICAgICBkYXRhID0gaG90ZWxzX3RyYWluaW5nKSAlPiUgCiAgIyBQcmUtcHJvY2Vzc2luZzoKICAjYWRkIGluZGljYXRvciB2YXJpYWJsZXMKICBzdGVwX211dGF0ZShjaGlsZHJlbl9pbmRpY2F0b3IgPSBpZmVsc2UoY2hpbGRyZW4gPiAwLCAxLCAwKSwKICAgICAgICAgICAgICBiYWJpZXNfaW5kaWNhdG9yID0gaWZlbHNlKGJhYmllcyA+IDAsIDEsIDApLAogICAgICAgICAgICAgIHByZXZpb3VzX2NhbmNlbGxhdGlvbnNfaW5kaWNhdG9yID0gaWZlbHNlKHByZXZpb3VzX2NhbmNlbGxhdGlvbnMgPiAwLCAxLCAwKSkgJT4lIAogIHN0ZXBfbXV0YXRlKGFnZW50X2luZGljYXRvciA9IGlmZWxzZShhZ2VudCA9PSAiTlVMTCIsIDEsIDApLAogICAgICAgICAgICAgIGNvbXBhbnlfaW5kaWNhdG9yID0gaWZlbHNlKGNvbXBhbnkgPT0gIk5VTEwiLCAxLCAwKSkgJT4lIAogIAogICNsdW1wIHRvZ2V0aGVyIGNvdW50cmllcyB0aGF0IGFyZW4ndCBpbiB0aGUgdG9wIDUKICBzdGVwX211dGF0ZShjb3VudHJ5ID0gZmN0X2x1bXBfbihjb3VudHJ5LCA1LCB3ID0gTlVMTCwgb3RoZXJfbGV2ZWwgPSAiT1RIRVIiKSkgJT4lIAogIAogIHN0ZXBfcm0oY2hpbGRyZW4sIGJhYmllcywgcHJldmlvdXNfY2FuY2VsbGF0aW9ucywgYWdlbnQsIGNvbXBhbnkpICU+JSAgI25lZWRzIHRvIGJlIGJlZm9yZSBzdGVwX25vcm1hbGl6ZSBvciBuZXh0IGNvZGUgY2h1bmsgd29uJ3Qgd29yawogIAogICN1c2Ugc3RlcF9ub3JtYWxpemUKICBzdGVwX25vcm1hbGl6ZShhbGxfcHJlZGljdG9ycygpLCAKICAgICAgICAgICAgICAgICAtYWxsX25vbWluYWwoKSkgJT4lIAogIAogICNjcmVhdGUgZHVtbXkgdmFyaWFibGVzCiAgc3RlcF9kdW1teShhbGxfbm9taW5hbCgpLCAKICAgICAgICAgICAgIC1hbGxfb3V0Y29tZXMoKSkKICAKICAKYGBgCgoKYGBge3IgdGVzdF9yZWNpcGV9CmhvdGVsc19yZWNpcGUgJT4lIAogIHByZXAoaG90ZWxzX3RyYWluaW5nKSAlPiUKICBqdWljZSgpIApgYGAKCgo1LiBJbiB0aGlzIHN0ZXAgd2Ugd2lsbCBzZXQgdXAgYSBMQVNTTyBtb2RlbCBhbmQgd29ya2Zsb3cuCgoqIEluIGdlbmVyYWwsIHdoeSB3b3VsZCB3ZSB3YW50IHRvIHVzZSBMQVNTTyBpbnN0ZWFkIG9mIHJlZ3VsYXIgbG9naXN0aWMgcmVncmVzc2lvbj8gKEhJTlQ6IHRoaW5rIGFib3V0IHdoYXQgaGFwcGVucyB0byB0aGUgY29lZmZpY2llbnRzKS4gIAoKPiBMQVNTTyBtb2RlbHMgcmVpbiBpbiBydW5hd2F5IGNvZWZmaWNpZW50cyBhbmQgc3RhbmRhcmRpemUgYWxsIHZhcmlhYmxlcyB0byBiZSBvbiBhIHNjYWwgd2l0aCBhIG1lYW4gb2YgMCBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDEgd2hpbGUgd2VhbmluZyBvdXQgYmFkIHByZWRpY3RvcnMuCgoqIERlZmluZSB0aGUgbW9kZWwgdHlwZSwgc2V0IHRoZSBlbmdpbmUsIHNldCB0aGUgYHBlbmFsdHlgIGFyZ3VtZW50IHRvIGB0dW5lKClgIGFzIGEgcGxhY2Vob2xkZXIsIGFuZCBzZXQgdGhlIG1vZGUuICAKCmBgYHtyIGxhc3NvX21vZH0KaG90ZWxzX2xhc3NvX21vZCA8LSAKICAjIERlZmluZSBhIGxhc3NvIG1vZGVsIAogIGxvZ2lzdGljX3JlZyhtaXh0dXJlID0gMSkgJT4lIAogICMgU2V0IHRoZSBlbmdpbmUgdG8gImdsbW5ldCIgCiAgc2V0X2VuZ2luZSgiZ2xtbmV0IikgJT4lIAogICMgVGhlIHBhcmFtZXRlcnMgd2Ugd2lsbCB0dW5lLgogIHNldF9hcmdzKHBlbmFsdHkgPSB0dW5lKCkpICU+JSAKICBzZXRfbW9kZSgiY2xhc3NpZmljYXRpb24iKQpgYGAKCiogQ3JlYXRlIGEgd29ya2Zsb3cgd2l0aCB0aGUgcmVjaXBlIGFuZCBtb2RlbC4gIApgYGB7cn0KaG90ZWxzX2xhc3NvX3dmIDwtIAogICMgU2V0IHVwIHRoZSB3b3JrZmxvdwogIHdvcmtmbG93KCkgJT4lIAogICMgQWRkIHRoZSByZWNpcGUKICBhZGRfcmVjaXBlKGhvdGVsc19yZWNpcGUpICU+JSAKICAjIEFkZCB0aGUgbW9kZWxpbmcKICBhZGRfbW9kZWwoaG90ZWxzX2xhc3NvX21vZCkKCmhvdGVsc19sYXNzb193ZgpgYGAKCgoKNi4gSW4gdGhpcyBzdGVwLCB3ZSdsbCB0dW5lIHRoZSBtb2RlbCBhbmQgZml0IHRoZSBtb2RlbCB1c2luZyB0aGUgYmVzdCB0dW5pbmcgcGFyYW1ldGVyIHRvIHRoZSBlbnRpcmUgdHJhaW5pbmcgZGF0YXNldC4KCiogQ3JlYXRlIGEgNS1mb2xkIGNyb3NzLXZhbGlkYXRpb24gc2FtcGxlLiBXZSdsbCB1c2UgdGhpcyBsYXRlci4gSSBoYXZlIHNldCB0aGUgc2VlZCBmb3IgeW91LiAgCiogVXNlIHRoZSBgZ3JpZF9yZWd1bGFyKClgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIGdyaWQgb2YgMTAgcG90ZW50aWFsIHBlbmFsdHkgcGFyYW1ldGVycyAod2UncmUga2VlcGluZyB0aGlzIHNvcnQgb2Ygc21hbGwgYmVjYXVzZSB0aGUgZGF0YXNldCBpcyBwcmV0dHkgbGFyZ2UpLiBVc2UgdGhhdCB3aXRoIHRoZSA1LWZvbGQgY3YgZGF0YSB0byB0dW5lIHRoZSBtb2RlbC4gIAoqIFVzZSB0aGUgYHR1bmVfZ3JpZCgpYCBmdW5jdGlvbiB0byBmaXQgdGhlIG1vZGVscyB3aXRoIGRpZmZlcmVudCB0dW5pbmcgcGFyYW1ldGVycyB0byB0aGUgZGlmZmVyZW50IGNyb3NzLXZhbGlkYXRpb24gc2V0cy4gIAoqIFVzZSB0aGUgYGNvbGxlY3RfbWV0cmljcygpYCBmdW5jdGlvbiB0byBjb2xsZWN0IGFsbCB0aGUgbWV0cmljcyBmcm9tIHRoZSBwcmV2aW91cyBzdGVwIGFuZCBjcmVhdGUgYSBwbG90IHdpdGggdGhlIGFjY3VyYWN5IG9uIHRoZSB5LWF4aXMgYW5kIHRoZSBwZW5hbHR5IHRlcm0gb24gdGhlIHgtYXhpcy4gUHV0IHRoZSB4LWF4aXMgb24gdGhlIGxvZyBzY2FsZS4gIAoqIFVzZSB0aGUgYHNlbGVjdF9iZXN0KClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIGJlc3QgdHVuaW5nIHBhcmFtZXRlciwgZml0IHRoZSBtb2RlbCB1c2luZyB0aGF0IHR1bmluZyBwYXJhbWV0ZXIgdG8gdGhlIGVudGlyZSB0cmFpbmluZyBzZXQgKEhJTlQ6IGBmaW5hbGl6ZV93b3JrZmxvdygpYCBhbmQgYGZpdCgpYCksIGFuZCBkaXNwbGF5IHRoZSBtb2RlbCByZXN1bHRzIHVzaW5nIGBwdWxsX3dvcmtmbG93X2ZpdCgpYCBhbmQgYHRpZHkoKWAuIEFyZSB0aGVyZSBzb21lIHZhcmlhYmxlcyB3aXRoIGNvZWZmaWNpZW50cyBvZiAwPwoKYGBge3J9CnNldC5zZWVkKDQ5NCkgIyBmb3IgcmVwcm9kdWNpYmlsaXR5CgojY3Jvc3MtdmFsaWRhdGlvbiBzYW1wbGUKaG90ZWxzX2N2IDwtIHZmb2xkX2N2KGhvdGVsc190cmFpbmluZywgdiA9IDUpCmBgYAoKYGBge3J9CiNwZW5hbHR5IHBhcmFtZXRlcnMKcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcihwZW5hbHR5KCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gMTApCgojdHVuZV9ncmlkKCkKaG90ZWxzX2xhc3NvX3R1bmUgPC0gCiAgaG90ZWxzX2xhc3NvX3dmICU+JSAKICB0dW5lX2dyaWQoCiAgICByZXNhbXBsZXMgPSBob3RlbHNfY3YsCiAgICBncmlkID0gcGVuYWx0eV9ncmlkCiAgICApCmBgYAoKYGBge3J9CiNjb2xsZWN0X21ldHJpY3MoKQpjb2xsZWN0X21ldHJpY3MoaG90ZWxzX2xhc3NvX3R1bmUpCgojbWFrZSBhIHBsb3QKaG90ZWxzX2xhc3NvX3R1bmUgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpICU+JSAKICBmaWx0ZXIoLm1ldHJpYyA9PSAiYWNjdXJhY3kiKSAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gcGVuYWx0eSwgeSA9IG1lYW4pKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9sb2cxMCgKICAgYnJlYWtzID0gc2NhbGVzOjp0cmFuc19icmVha3MoImxvZzEwIiwgZnVuY3Rpb24oeCkgMTBeeCksCiAgIGxhYmVscyA9IHNjYWxlczo6dHJhbnNfZm9ybWF0KCJsb2cxMCIsc2NhbGVzOjptYXRoX2Zvcm1hdCgxMF4ueCkpKSArCiAgbGFicyh4ID0gInBlbmFsdHkgKGxvZykiLCB5ID0gImFjY3VyYWN5IikKYGBgCgpgYGB7ciB0dW5lfQojc2VsZWN0X2Jlc3QoKQpiZXN0X3BhcmFtIDwtIGhvdGVsc19sYXNzb190dW5lICU+JSAKICBzZWxlY3RfYmVzdChtZXRyaWMgPSAiYWNjdXJhY3kiKQoKI0NyZWF0ZSBmaW5hbCB3b3JrZmxvdwpob3RlbHNfbGFzc29fZmluYWxfd2YgPC0gaG90ZWxzX2xhc3NvX3dmICU+JSAKICBmaW5hbGl6ZV93b3JrZmxvdyhiZXN0X3BhcmFtKQpgYGAKCmBgYHtyIGxhc3NvX3RyYWlufQpob3RlbHNfbGFzc29fZmluYWxfbW9kIDwtIGhvdGVsc19sYXNzb19maW5hbF93ZiAlPiUgCiAgZml0KGRhdGEgPSBob3RlbHNfdHJhaW5pbmcpCgpob3RlbHNfbGFzc29fZmluYWxfbW9kICU+JSAKICBwdWxsX3dvcmtmbG93X2ZpdCgpICU+JSAKICB0aWR5KCkgCmBgYAoKPiBBcnJpdmFsX2RhdGVfbW9udGhfRmVicnVhcnksIG1hcmtldF9zZWdtZW50X0dyb3VwcywgZGlzdHJpYnV0aW9uX2NoYW5uZWxfVW5kZWZpbmVkLCBhbmQgYXNzaWduZWRfcm9vbV90eXBlX0wgZWFjaCBoYXZlIGEgY29lZmZpY2llbnQgb2YgMC4KCgo3LiBOb3cgdGhhdCB3ZSBoYXZlIGEgbW9kZWwsIGxldCdzIGV2YWx1YXRlIGl0IGEgYml0IG1vcmUuIEFsbCB3ZSBoYXZlIGxvb2tlZCBhdCBzbyBmYXIgaXMgdGhlIGNyb3NzLXZhbGlkYXRlZCBhY2N1cmFjeSBmcm9tIHRoZSBwcmV2aW91cyBzdGVwLiAKCiogQ3JlYXRlIGEgdmFyaWFibGUgaW1wb3J0YW5jZSBncmFwaC4gV2hpY2ggdmFyaWFibGVzIHNob3cgdXAgYXMgdGhlIG1vc3QgaW1wb3J0YW50PyBBcmUgeW91IHN1cnByaXNlZD8gIAoKPiBJbiBnZW5lcmFsLCByZXNlcnZlZCBhbmQgYXNzaWduZWQgcm9vbSB0eXBlcyBhcHBlYXIgdG8gYmUgYW1vbmcgdGhlIG1vc3QgaW1wb3J0YW50IHZhcmlhYmxlcyAod2hpY2ggSSB3YXMgbm90IGV4cGVjdGluZykuIFRoZSBkZXBvc2l0IGJlaW5nIG5vbi1yZWZ1bmRhYmxlIGlzIHRoZSBzZWNvbmQgbW9zdCBpbXBvcnRhbnQsIHdoaWNoIGlzIGV4cGVjdGVkLiBGb3Igc29tZSByZWFzb24sIHRoZSB0ZW50aCBtb3N0IGltcG9ydGFudCB2YXJpYWJsZSBpcyBpZiB0aGUgZ3Vlc3RzIGFyZSBmcm9tIFBvcnR1Z2FsLCB3aGljaCBpcyBhbHNvIHN1cnByaXNpbmcuCgpgYGB7ciB2aXB9CiMgVmlzdWFsaXplIHZhcmlhYmxlIGltcG9ydGFuY2UKaG90ZWxzX2xhc3NvX2ZpbmFsX21vZCAlPiUgCiAgcHVsbF93b3JrZmxvd19maXQoKSAlPiUgCiAgdmlwKCkKYGBgCgoKKiBVc2UgdGhlIGBsYXN0X2ZpdCgpYCBmdW5jdGlvbiB0byBmaXQgdGhlIGZpbmFsIG1vZGVsIGFuZCB0aGVuIGFwcGx5IGl0IHRvIHRoZSB0ZXN0aW5nIGRhdGEuIFJlcG9ydCB0aGUgbWV0cmljcyBmcm9tIHRoZSB0ZXN0aW5nIGRhdGEgdXNpbmcgdGhlIGBjb2xsZWN0X21ldHJpY3MoKWAgZnVuY3Rpb24uIEhvdyBkbyB0aGV5IGNvbXBhcmUgdG8gdGhlIGNyb3NzLXZhbGlkYXRlZCBtZXRyaWNzPwoKPiBUaGV5IGFyZSB2ZXJ5IHNpbWlsYXIgdG8gdGhlIGNyb3NzLXZhbGlkYXRlZCBtZXRyaWNzIC0gdGhlc2UgbWV0cmljcyBhcmUgZWFjaCBhYm91dCAwLjAwMiBsb3dlciB0aGFuIHRoZSBlc3RpbWF0ZXMgZnJvbSB0aGUgY3Jvc3MtdmFsaWRhdGVkIG1ldHJpY3MuCgpgYGB7cn0KaG90ZWxzX2xhc3NvX3Rlc3QgPC0gaG90ZWxzX2xhc3NvX2ZpbmFsX3dmICU+JSAKICBsYXN0X2ZpdChob3RlbHNfbW9kX3NwbGl0KQoKaG90ZWxzX2xhc3NvX3Rlc3QgJT4lIAogIGNvbGxlY3RfbWV0cmljcygpCmBgYApgYGB7cn0KI2Nyb3NzLXZhbGlkYXRlZCBtZXRyaWNzIGZvciBjb21wYXJpc29uCmNvbGxlY3RfbWV0cmljcyhob3RlbHNfbGFzc29fdHVuZSkgJT4lIAogIGhlYWQoMikKYGBgCgoKKiBVc2UgdGhlIGBjb2xsZWN0X3ByZWRpY3Rpb25zKClgIGZ1bmN0aW9uIHRvIGZpbmQgdGhlIHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzIGFuZCBjbGFzc2VzIGZvciB0aGUgdGVzdCBkYXRhLiBTYXZlIHRoaXMgdG8gYSBuZXcgZGF0YXNldCBjYWxsZWQgYHByZWRzYC4gVGhlbiwgdXNlIHRoZSBgY29uZl9tYXQoKWAgZnVuY3Rpb24gZnJvbSBgZGlhbHNgIChwYXJ0IG9mIGB0aWR5bW9kZWxzYCkgdG8gY3JlYXRlIGEgY29uZnVzaW9uIG1hdHJpeCBzaG93aW5nIHRoZSBwcmVkaWN0ZWQgY2xhc3NlcyB2cy4gdGhlIHRydWUgY2xhc3Nlcy4gQ29tcHV0ZSB0aGUgdHJ1ZSBwb3NpdGl2ZSByYXRlIChzZW5zaXRpdml0eSksIHRydWUgbmVnYXRpdmUgcmF0ZSAoc3BlY2lmaWNpdHkpLCBhbmQgYWNjdXJhY3kuIFNlZSB0aGlzIFtXaWtpcGVkaWFdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0NvbmZ1c2lvbl9tYXRyaXgpIHJlZmVyZW5jZSBpZiB5b3UgKGxpa2UgbWUpIHRlbmQgdG8gZm9yZ2V0IHRoZXNlIGRlZmluaXRpb25zLiBBbHNvIGtlZXAgaW4gbWluZCB0aGF0IGEgInBvc2l0aXZlIiBpbiB0aGlzIGNhc2UgaXMgYSBjYW5jZWxsYXRpb24gKHRob3NlIGFyZSB0aGUgMSdzKS4gICAgCgoKYGBge3J9CnByZWRzIDwtCiAgY29sbGVjdF9wcmVkaWN0aW9ucyhob3RlbHNfbGFzc29fdGVzdCkKCgpwcmVkcyAlPiUgCiAgY29uZl9tYXQodHJ1dGggPSBpc19jYW5jZWxlZCwgZXN0aW1hdGUgPSAucHJlZF9jbGFzcykKYGBgCgo+IFNlbnNpdGl2aXR5ICh0cnVlIHBvc2l0aXZlIHJhdGUpID0gdHJ1ZSBwb3NpdGl2ZS8odHJ1ZSBwb3NpdGl2ZSArIGZhbHNlIG5lZ2F0aXZlKSA9IDE0LDMzMy8oMTQsMzMzKzc3NzcpID0gMC42NDgyNTg3ID0gNjQuODMlCgo+IFNwZWNpZmljaXR5ICh0cnVlIG5lZ2F0aXZlIHJhdGUpID0gdHJ1ZSBuZWdhdGl2ZS8odHJ1ZSBuZWdhdGl2ZSArIGZhbHNlIHBvc2l0aXZlKSA9IDM0LDE3OS8oMzQsMTc5KzM0MDQpID0gMC45MDk0MjcxID0gOTAuOTQlCgo+IEFjY3VyYWN5ID0gKHRydWUgcG9zaXRpdmUgKyB0cnVlIG5lZ2F0aXZlKS90b3RhbCA9IDAuODEyNjkxNiA9IDgxLjI3JQoKKiBVc2UgdGhlIGBwcmVkc2AgZGF0YXNldCB5b3UganVzdCBjcmVhdGVkIHRvIGNyZWF0ZSBhIGRlbnNpdHkgcGxvdCBvZiB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXRpZXMgb2YgY2FuY2VsaW5nICh0aGUgdmFyaWFibGUgaXMgY2FsbGVkIGAucHJlZF8xYCksIGZpbGxpbmcgYnkgYGlzX2NhbmNlbGVkYC4gVXNlIGFuIGBhbHBoYSA9IC41YCBhbmQgYGNvbG9yID0gTkFgIGluIHRoZSBgZ2VvbV9kZW5zaXR5KClgLgoKYGBge3J9CnByZWRzICU+JSAKICBnZ3Bsb3QoYWVzKHggPSAucHJlZF8xLCBmaWxsID0gaXNfY2FuY2VsZWQpKSArCiAgZ2VvbV9kZW5zaXR5KGFscGhhID0gLjUsIGNvbG9yID0gTkEpICsKICBsYWJzKHggPSAicHJlZGljdGVkIHByb2JhYmlsaXRpZXMgb2YgY2FuY2VsbGluZyIpCmBgYAoKCkFuc3dlciB0aGVzZSBxdWVzdGlvbnM6IAphLiBXaGF0IHdvdWxkIHRoaXMgZ3JhcGggbG9vayBsaWtlIGZvciBhIG1vZGVsIHdpdGggYW4gYWNjdXJhY3kgdGhhdCB3YXMgY2xvc2UgdG8gMT8gIAoKPiBBIG1vZGVsIHdpdGggYW4gYWNjdXJhY3kgY2xvc2UgdG8gMSB3b3VsZCBoYXZlIHRoZSByZWRzIChub3QgY2FuY2VsbGVkKSBiZSB2ZXJ5IGRlbnNlIGF0IDAgKDAlIHByb2JhYmlsaXR5IG9mIGNhbmNlbGxlZCkgYW5kIGxlc3MgZGVuc2UgZWxzZXdoZXJlLiBUaGUgYmx1ZXMgd291bGQgYmUgdmVyeSBkZW5zZSBhdCAxIGFuZCBsZXNzIGRlbnNlIGVsc2V3aGVyZS4gVGhlIHRhaWxzIG9mIGJvdGggd291bGQgc3RvcCBuZWFyIDAuNSAob3Igd2hhdGV2ZXIgdGhlIHRocmVzaG9sZCB3YXMgc2V0IGZvcikKCmIuIE91ciBwcmVkaWN0aW9ucyBhcmUgY2xhc3NpZmllZCBhcyBjYW5jZWxlZCBpZiB0aGVpciBwcmVkaWN0ZWQgcHJvYmFiaWxpdHkgb2YgY2FuY2VsaW5nIGlzIGdyZWF0ZXIgdGhhbiAuNS4gSWYgd2Ugd2FudGVkIHRvIGhhdmUgYSBoaWdoIHRydWUgcG9zaXRpdmUgcmF0ZSwgc2hvdWxkIHdlIG1ha2UgdGhlIGN1dG9mZiBmb3IgcHJlZGljdGVkIGFzIGNhbmNlbGVkIGhpZ2hlciBvciBsb3dlciB0aGFuIC41PwoKPiBXZSBzaG91bGQgbWFrZSBpdCBsb3dlciB0aGFuIDAuNSB0byBjYXB0dXJlIG1vcmUgb2YgdGhlIGNhbmNlbGxlZCB2aXNpdHMuCgpjLiBXaGF0IGhhcHBlbnMgdG8gdGhlIHRydWUgbmVnYXRpdmUgcmF0ZSBpZiB3ZSB0cnkgdG8gZ2V0IGEgaGlnaGVyIHRydWUgcG9zaXRpdmUgcmF0ZT8gCgo+IFRoZSB0cnVlIG5lZ2F0aXZlIHJhdGUgd291bGQgZGVjcmVhc2UuCgo4LiBMZXQncyBzYXkgdGhhdCB0aGlzIG1vZGVsIGlzIGdvaW5nIHRvIGJlIGFwcGxpZWQgdG8gYm9va2luZ3MgMTQgZGF5cyBpbiBhZHZhbmNlIG9mIHRoZWlyIGFycml2YWwgYXQgZWFjaCBob3RlbCwgYW5kIHNvbWVvbmUgd2hvIHdvcmtzIGZvciB0aGUgaG90ZWwgd2lsbCBtYWtlIGEgcGhvbmUgY2FsbCB0byB0aGUgcGVyc29uIHdobyBtYWRlIHRoZSBib29raW5nLiBEdXJpbmcgdGhpcyBwaG9uZSBjYWxsLCB0aGV5IHdpbGwgdHJ5IHRvIGVuc3VyZSB0aGF0IHRoZSBwZXJzb24gd2lsbCBiZSBrZWVwaW5nIHRoZWlyIHJlc2VydmF0aW9uIG9yIHRoYXQgdGhleSB3aWxsIGJlIGNhbmNlbGluZyBpbiB3aGljaCBjYXNlIHRoZXkgY2FuIGRvIHRoYXQgbm93IGFuZCBzdGlsbCBoYXZlIHRpbWUgdG8gZmlsbCB0aGUgcm9vbS4gSG93IHNob3VsZCB0aGUgaG90ZWwgZ28gYWJvdXQgZGVjaWRpbmcgd2hvIHRvIGNhbGw/IEhvdyBjb3VsZCB0aGV5IG1lYXN1cmUgd2hldGhlciBpdCB3YXMgd29ydGggdGhlIGVmZm9ydCB0byBkbyB0aGUgY2FsbGluZz8gQ2FuIHlvdSB0aGluayBvZiBhbm90aGVyIHdheSB0aGV5IG1pZ2h0IHVzZSB0aGUgbW9kZWw/IAoKPiBUaGUgaG90ZWwgY291bGQgdXNlIHRoZSBtb2RlbCB0byBpZGVudGlmeSBhbmQgY2FsbCBwZW9wbGUgd2l0aCBhIHByZWRpY3RlZCBwcm9iYWJpbGl0eSBvZiBjYW5jZWxsaW5nIGFib3ZlIGEgY2VydGFpbiB0aHJlc2hvbGQgKHNheSwgMC41KS4gVG8gbWVhc3VyZSB3aGV0aGVyIGl0IHdhcyB3b3J0aCB0aGUgZWZmb3J0IHRvIGRvIHRoZSBjYWxsaW5nLCB0aGUgaG90ZWwgY291bGQga2VlcCB0cmFjayBvZiB0aGUgcmF0ZSBvZiBwZW9wbGUgY2FsbGVkIHRoYXQgY2FuY2VsbGVkIHZzIHRoZSByYXRlIG9mIHBlb3BsZSB3aG8gd2VyZSBub3QgY2FsbGVkIHdobyBlbmRlZCB1cCBjYW5jZWxsaW5nLiBJdCdzIGFsc28gd29ydGggY29uc2lkZXJpbmcgdGhhdCB0aGUgcGhvbmUgY2FsbCBtYXkgYmUgYSByZW1pbmRlciB0byBwZW9wbGUgd2hvIHNvbWVob3cgZm9yZ290IGFib3V0IHRoZWlyIHJlc2VydmF0aW9uLCBzbyB0aGUgbW9kZWwgc2hvdWxkIGJlIHJld29ya2VkIHdpdGggdGhlIG5ldyBkYXRhIG92ZXJ0aW1lLiBBbHRob3VnaCBJIHRoaW5rIGl0IHdvdWxkIGJlIGluYXBwcm9wcmlhdGUgYW5kIGxpa2VseSBpbGxlZ2FsLCB0aGUgbW9kZWwgY291bGQgYmUgdXNlZCB0byBzZXQgdGhlIGRlcG9zaXQgYW1vdW50IG9uIGEgc2xpZGluZyBzY2FsZS4gVGhvc2Ugd2hvIGFyZSBtb3JlIGxpa2VseSB0byBjYW5jZWwgY291bGQgYmUgYXNrZWQgdG8gcGF5IGEgaGlnaGVyIGRlcG9zaXQsIGRlY3JlYXNpbmcgdGhlIGxpa2VsaWhvb2QgdGhhdCB0aGV5IHdpbGwgY2FuY2VsIGFuZCBkZWNyZWFzaW5nIHRoZSBob3RlbCdzIGxvc3NlcyBpZiB0aGV5IGRvLiAKCjkuIEhvdyBtaWdodCB5b3UgZ28gYWJvdXQgcXVlc3Rpb25pbmcgYW5kIGV2YWx1YXRpbmcgdGhlIG1vZGVsIGluIHRlcm1zIG9mIGZhaXJuZXNzPyBBcmUgdGhlcmUgYW55IHF1ZXN0aW9ucyB5b3Ugd291bGQgbGlrZSB0byBhc2sgb2YgdGhlIHBlb3BsZSB3aG8gY29sbGVjdGVkIHRoZSBkYXRhPyAKCj4gSXQgd291bGQgYmUgaW1wb3J0YW50IHRvIGNvbnNpZGVyIHRoZSBkZW1vZ3JhcGhpY3Mgb2YgdGhlIHBlb3BsZSBjb2xsZWN0ZWQsIGVzcGVjaWFsbHkgYmVjYXVzZSB3aGF0IGNvdW50cnkgdGhleSdyZSBmcm9tIGlzIGEgcHJlZGljdG9yLiBCZWNhdXNlIHdlJ3JlIG9ubHkgY29uc2lkZXJpbmcgdGhlIDUgbW9zdCBjb21tb24gY291bnRyaWVzIGFuZCBhbGwgb3RoZXJzIGFyZSBsdW1wZWQgdG9nZXRoZXIsIHRoZSBpbmZvcm1hdGlvbiBjb3VsZCBiZSBza2V3ZWQgZW5vdWdoIHRoYXQgcGVvcGxlIGZyb20gY2VydGFpbiBjb3VudHJpZXMgYXJlIHRhcmdldGVkLiBJIHRoaW5rIGl0J3MgYWxzbyBjb25jZXJuaW5nIGhvdyB1bmV2ZW4gdGhlIGRpdmlkZSBvZiBkYXRhIGJldHdlZW4gdGhlIHR3byBob3RlbHMgd2FzIC0gYWJvdXQgMi8zIGNhbWUgZnJvbSB0aGUgY2l0eSBob3RlbCwgbWVhbmluZyBpdCBhbmQgaXRzIGd1ZXN0cyB3ZXJlIG92ZXJyZXByZXNlbnRlZC4gSSdkIGJlIGN1cmlvdXMgdG8gYXNrIGhvdyB0aG9zZSB0d28gaG90ZWxzIGluIHBhcnRpY3VsYXIgd2VyZSBjaG9zZW4gdG8gYmUgcGFydCBvZiB0aGUgZGF0YXNldCwgd2hldGhlciB0aGVyZSB3ZXJlIGFueSBkaWZmZXJlbmNlcyBpbiBzdW1tZXIgYXMgb3Bwb3NlZCB0byBvdGhlciBzZWFzb25zIChiZWNhdXNlIHN1bW1lciBtb250aHMgd2VyZSBvdmVycmVwcmVzZW50ZWQpLCBhbmQgaWYgdGhlcmUgd2VyZSBhbnkgdmFyaWFibGVzIHRoZXkgY29uc2lkZXJlZCBpbmNsdWRpbmcgYnV0IGRpZG4ndC4KCgoKIyMgQmlhcyBhbmQgRmFpcm5lc3MKClJlYWQgW0NoYXB0ZXIgMTogVGhlIFBvd2VyIENoYXB0ZXJdKGh0dHBzOi8vZGF0YS1mZW1pbmlzbS5taXRwcmVzcy5taXQuZWR1L3B1Yi92aThvYnhoNy9yZWxlYXNlLzQpIG9mIERhdGEgRmVtaW5pc20gYnkgQ2F0aGVyaW5lIEQnSWduYXppbyBhbmQgTGF1cmVuIEtsZWluLiBXcml0ZSBhIDQtNiBzZW50ZW5jZSBwYXJhZ3JhcGggcmVmbGVjdGluZyBvbiB0aGlzIGNoYXB0ZXIuIEFzIHlvdSByZWZsZWN0LCB5b3UgbWlnaHQgY29uc2lkZXIgcmVzcG9uZGluZyB0byB0aGVzZSBzcGVjaWZpYyBxdWVzdGlvbnMuIFdlIHdpbGwgYWxzbyBoYXZlIGEgZGlzY3Vzc2lvbiBhYm91dCB0aGVzZSBxdWVzdGlvbnMgaW4gY2xhc3Mgb24gVGh1cnNkYXkuCgoqIEF0IHRoZSBlbmQgb2YgdGhlICJNYXRyaXggb2YgRG9taW5hdGlvbiIgc2VjdGlvbiwgdGhleSBlbmNvdXJhZ2UgdXMgdG8gImFzayB1bmNvbWZvcnRhYmxlIHF1ZXN0aW9uczogd2hvIGlzIGRvaW5nIHRoZSB3b3JrIG9mIGRhdGEgc2NpZW5jZSAoYW5kIHdobyBpcyBub3QpPyBXaG9zZSBnb2FscyBhcmUgcHJpb3JpdGl6ZWQgaW4gZGF0YSBzY2llbmNlIChhbmQgd2hvc2UgYXJlIG5vdCk/IEFuZCB3aG8gYmVuZWZpdHMgZnJvbSBkYXRhIHNjaWVuY2UgKGFuZCB3aG8gaXMgZWl0aGVyIG92ZXJsb29rZWQgb3IgYWN0aXZlbHkgaGFybWVkKT8iIEluIGdlbmVyYWwsIGhvdyB3b3VsZCB5b3UgYW5zd2VyIHRoZXNlIHF1ZXN0aW9ucz8gQW5kIHdoeSBhcmUgdGhleSBpbXBvcnRhbnQ/ICAKKiBDYW4geW91IHRoaW5rIG9mIGFueSBleGFtcGxlcyBvZiBtaXNzaW5nIGRhdGFzZXRzLCBsaWtlIHRob3NlIGRlc2NyaWJlZCBpbiB0aGUgIkRhdGEgU2NpZW5jZSBmb3IgV2hvbT8iIHNlY3Rpb24/IE9yIHdhcyB0aGVyZSBhbiBleGFtcGxlIHRoZXJlIHRoYXQgc3VycHJpc2VkIHlvdT8gIAoqIEhvdyBkaWQgdGhlIGV4YW1wbGVzIGluIHRoZSAiRGF0YSBTY2llbmNlIHdpdGggV2hvc2UgSW50ZXJlc3RzIGFuZCBHb2Fscz8iIHNlY3Rpb24gbWFrZSB5b3UgZmVlbD8gV2hhdCByZXNwb25zaWJpbGl0eSBkbyBjb21wYW5pZXMgaGF2ZSB0byBwcmV2ZW50IHRoZXNlIHRoaW5ncyBmcm9tIG9jY3VycmluZz8gV2hvIGlzIHRvIGJsYW1lPwoKPiBBcyB0aGUgYXJ0aWNsZSBtZW50aW9ucywgdGhlIGFuc3dlciB0byB0aGUgcXVlc3Rpb24gb2YgIndobyBpcyBkb2luZyB0aGUgd29yayBvZiBkYXRhIHNjaWVuY2U/IiBpcywgaW4gYW4gb2ZmaWNpYWwgY2FwYWNpdHksIG92ZXJ3aGVsbWluZ2x5IHdoaXRlIG1lbi4gSG93ZXZlciwgdGhlIGFydGljbGUgYWxzbyBnYXZlIHNldmVyYWwgZXhhbXBsZXMgb2YgcGVvcGxlIHdobyBJIGNvbnNpZGVyIHRvIGJlIGRvaW5nIHRoZSAicmVhbCIgd29yayBvZiBkYXRhIHNjaWVuY2UsIHBlb3BsZSB3aG8sIHJhdGhlciB0aGFuIHdvcmtpbmcgYXQgbGFyZ2UgY29tcGFuaWVzIHRoYXQgZ2VuZXJhbGx5IHZpb2xhdGUgY29uc3VtZXIgcHJpdmFjeSwgYXJlIGFkdm9jYXRpbmcgZm9yIGNoYW5nZXMgaW4gdGhlIHdheSBkYXRhIGlzIGNvbGxlY3RlZCBhbmQgYW5hbHl6ZWQuIFRoZSBhcnRpY2xlIGdhdmUgbnVtZXJvdXMgZXhhbXBsZXMgb2YgaG93IGRhdGEgc2NpZW5jZSBhY3RpdmVseSBoYXJtcyBjZXJ0YWluIGdyb3VwcywgZnJvbSBBSSBmYWNpYWwgcmVjb2duaXRpb24gc29mdHdhcmUgYmVpbmcgdXNlZCBpbiBzdXJ2ZWlsbGFuY2Ugc3RhdGVzIHRvIHBvb3IgcGFyZW50cyBiZWluZyBtb3JlIGxpa2VseSB0byBiZSBhY2N1c2VkIG9mIGNoaWxkIGFidXNlIGJlY2F1c2Ugb2YgdGhlaXIgY2xhc3MuIEluIHNvbWUgY2FzZXMsIGxlc3MgYmlhc2VkIGRhdGEgY29sbGVjdGlvbiBjYW4gaGVscCB0byBzb2x2ZSB0aGVzZSBwcm9ibGVtcywgYnV0IGluIG90aGVycyAoZXNwZWNpYWxseSB0aGUgZmFjaWFsIHJlY29nbml0aW9uIHNvZnR3YXJlcyBhbmQgb3RoZXJzIHRoYXQgaW5mcmluZ2Ugb24gcHJpdmFjeSkgd2UgbXVzdCBjb25zaWRlciB3aGV0aGVyIHN1Y2ggYW5hbHlzaXMgc2hvdWxkIGJlIGRvbmUgYXQgYWxsLgoKCg==